package com.supermap.fabricsdk;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.protobuf.InvalidProtocolBufferException;
import com.supermap.Utils;
import org.hyperledger.fabric.protos.ledger.rwset.kvrwset.KvRwset;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;

import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.logging.Logger;

import static org.hyperledger.fabric.sdk.BlockInfo.EnvelopeType.TRANSACTION_ENVELOPE;

/**
 * @author liqs
 * @version 1.0
 * @date 2020/8/19 10:49
 * 直接遍历区块获取历史信息
 */
public class SmGetInfoFromblock {

    Logger logger = Logger.getLogger(SmGetInfoFromblock.class.toString());
    private HFClient hfClient;
    private Channel channel;
    private BlockchainInfo blockchainInfo;
    private int height;
    private JSONArray jsonArray = new JSONArray();


    public SmGetInfoFromblock(HFClient hfClient, Channel channel) {
        this.hfClient = hfClient;
        this.channel = channel;
        try {
            blockchainInfo = this.channel.queryBlockchainInfo();
            height = (int)blockchainInfo.getHeight();
        } catch (ProposalException e) {
            e.printStackTrace();
        } catch (InvalidArgumentException e) {
            e.printStackTrace();
        }
    }

    public JSONArray getHistory(String queryKey){
        // 前 3 个区块为创建网络时的信息
        for (int i = height - 1; i > 3 ; i--) {
            try {
                BlockInfo blockInfo = channel.queryBlockByNumber(i);
                getHistoryByKeyFromBlock(blockInfo, queryKey);
            } catch (InvalidArgumentException e) {
                e.printStackTrace();
            } catch (ProposalException e) {
                e.printStackTrace();
            }
        }
        return this.jsonArray;
    }

    /**
     * 从初始区块开始遍历得到一个 Key 的历史信息
     *
     * @param blockInfo
     * @return
     */
    private void getHistoryByKeyFromBlock(BlockInfo blockInfo, String queryKey) {

        for (BlockInfo.EnvelopeInfo envelopeInfo : blockInfo.getEnvelopeInfos()) {
            if (envelopeInfo.getType() == TRANSACTION_ENVELOPE) {
                Date timestamp = envelopeInfo.getTimestamp();
                BlockInfo.TransactionEnvelopeInfo transactionEnvelopeInfo = (BlockInfo.TransactionEnvelopeInfo) envelopeInfo;
                String transactionID = transactionEnvelopeInfo.getTransactionID();
                for (BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo transactionActionInfo : transactionEnvelopeInfo.getTransactionActionInfos()) {
                    JSONObject jsonObject = new JSONObject();
                    // 获取每次使用的链码方法和存储的 key 与 value
                    int chaincodeInputArgsCount = transactionActionInfo.getChaincodeInputArgsCount();
                    String[] argus = new String[chaincodeInputArgsCount];
                    for (int i = 0; i < chaincodeInputArgsCount; i++) {
                        argus[i] = new String(transactionActionInfo.getChaincodeInputArgs(i));
                    }
                    if(!argus[1].equals(queryKey)){
                        break;
                    }
                    jsonObject.put("Record", argus[2]);
                    // 交易ID
                    jsonObject.put("TxId", transactionID);
                    // 时间格式不对
                    //                    transactionMap.put("timestamp", timestamp.getTime());  1597825447137
                    //                    Timestamp : = 2020-08-19 08:01:17.953448426 +0000 UTC
                    jsonObject.put("Timestamp", Utils.stampToData(timestamp));
                    jsonArray.add(jsonObject);
                }
            }
        }
    }

    /**
     * 获取区块并解析为读写集
     *
     * @param blockInfo
     * @return
     */
    public List<Map> getRWSetFromBlock(BlockInfo blockInfo) throws  UnsupportedEncodingException {

        List<Map> transactionList = new ArrayList<>();
        for (BlockInfo.EnvelopeInfo envelopeInfo : blockInfo.getEnvelopeInfos()) {
            String id = envelopeInfo.getCreator().getId();
            String mspid = envelopeInfo.getCreator().getMspid();

            if (envelopeInfo.getType() == TRANSACTION_ENVELOPE) {
                Date timestamp = envelopeInfo.getTimestamp();
                BlockInfo.TransactionEnvelopeInfo transactionEnvelopeInfo = (BlockInfo.TransactionEnvelopeInfo) envelopeInfo;
                String transactionID = transactionEnvelopeInfo.getTransactionID();
                boolean valid = transactionEnvelopeInfo.isValid();
                byte validationCode = transactionEnvelopeInfo.getValidationCode();
                for (BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo transactionActionInfo : transactionEnvelopeInfo.getTransactionActionInfos()) {
                    Map<String, Object> transactionMap = new HashMap<>();
                    transactionMap.put("transactionID", transactionID);
                    transactionMap.put("timestamp", timestamp.getTime());
                    transactionMap.put("isValid", valid);
                    transactionMap.put("MSPID", mspid);
                    transactionMap.put("usercert", id);
                    transactionMap.put("validationCode", validationCode);
                    int index = validationCode;
//                    if (index <= 24) {
//                        transactionMap.put("validationCodeName", TransactionValidCode.transactionValidCode[index]);
//                    } else {
//                        transactionMap.put("validationCodeName", TransactionValidCode.transactionValidCode[index-229]);
//                    }
                    int chaincodeInputArgsCount = transactionActionInfo.getChaincodeInputArgsCount();

                    // 获取每次使用的链码方法和存储的 key 与 value
                    String[] argus = new String[chaincodeInputArgsCount];
                    for (int i = 0; i < chaincodeInputArgsCount; i++) {
                        argus[i] = new String(transactionActionInfo.getChaincodeInputArgs(i));
                    }
                    transactionMap.put("argus", argus);


                    transactionMap.put("status", transactionActionInfo.getResponseStatus());
                    transactionMap.put("endorsementsCount", transactionActionInfo.getEndorsementsCount());
                    String chaincodeIDName = transactionActionInfo.getChaincodeIDName();
                    transactionMap.put("chaincodeName", chaincodeIDName);
                    String chaincodeIDVersion = transactionActionInfo.getChaincodeIDVersion();
                    transactionMap.put("chaincodeVersion", chaincodeIDVersion);
                    // 解析得到每一次交易的 key-value
                    TxReadWriteSetInfo rwsetInfo = transactionActionInfo.getTxReadWriteSet();
                    if (null != rwsetInfo) {
                        List<Map> rwList = new ArrayList<Map>();

                        for (TxReadWriteSetInfo.NsRwsetInfo nsRwsetInfo : rwsetInfo.getNsRwsetInfos()) {

                            Map<String, Object> rwMap = new HashMap<>();
                            Map<String, String> writeMap = new HashMap<>();
                            KvRwset.KVRWSet rws = null;
                            try {
                                rws = nsRwsetInfo.getRwset();
                            } catch (InvalidProtocolBufferException e) {
                                e.printStackTrace();
                            }
                            String[] readSet = new String[rws.getReadsCount()];
                            int i = 0;
                            for (KvRwset.KVRead readList : rws.getReadsList()) {
                                String key = readList.getKey();
                                readSet[i++] = key;
                            }
                            rwMap.put("read", readSet);
                            for (KvRwset.KVWrite writeList : rws.getWritesList()) {
                                String valAsString = printableString(new String(writeList.getValue().toByteArray(), "UTF-8"));
                                writeList.getKey();
                                writeMap.put(writeList.getKey(), valAsString);
                            }
                            rwMap.put("write", writeMap);
                            rwList.add(rwMap);

                        }
                        transactionMap.put("RWSet", rwList);
                    }
                    transactionList.add(transactionMap);
                }
            }

        }
        return transactionList;
    }

    /**
     * 只返回该区块里面包含的交易信息
     *
     * @param blockInfo
     * @return
     */
    public JSONArray getTcKVFromBlock(BlockInfo blockInfo) throws  UnsupportedEncodingException {
        JSONArray jsonArray = new JSONArray();
        for (BlockInfo.EnvelopeInfo envelopeInfo : blockInfo.getEnvelopeInfos()) {
            if (envelopeInfo.getType() == TRANSACTION_ENVELOPE) {
                BlockInfo.TransactionEnvelopeInfo transactionEnvelopeInfo = (BlockInfo.TransactionEnvelopeInfo) envelopeInfo;
                for (BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo transactionActionInfo : transactionEnvelopeInfo.getTransactionActionInfos()) {
                    int chaincodeInputArgsCount = transactionActionInfo.getChaincodeInputArgsCount();

                    // 获取每次使用的链码方法和存储的 key 与 value
                    String[] argus = new String[chaincodeInputArgsCount];
                    for (int i = 0; i < chaincodeInputArgsCount; i++) {
                        argus[i] = new String(transactionActionInfo.getChaincodeInputArgs(i));
                    }
                    jsonArray.add(argus);
                }
            }
        }
        return jsonArray;
    }

    private String printableString(String string) {
        int maxLogStringLength = 64;
        if (string == null || string.length() == 0) {
            return string;
        }
        String ret = string.replaceAll("[^\\p{Print}]", "?");
        // 解析时不需要这一句。因为我们的数据集里面有冒号等  会赵成错误的数据截断
//        ret = ret.substring(0, Math.min(ret.length(), maxLogStringLength)) + (ret.length() > maxLogStringLength ? "..." : "");
        return ret;
    }

    private String[] transactionValidCode = new String[]{
            "VALID","NIL_ENVELOPE_VALUE","BAD_PAYLOAD_VALUE","BAD_COMMON_HEADER_VALUE",
            "BAD_CREATOR_SIGNATURE_VALUE","INVALID_ENDORSER_TRANSACTION_VALUE","INVALID_CONFIG_TRANSACTION_VALUE","UNSUPPORTED_TX_PAYLOAD_VALUE",
            "BAD_PROPOSAL_TXID_VALUE","DUPLICATE_TXID_VALUE","ENDORSEMENT_POLICY_FAILURE_VALUE","MVCC_READ_CONFLICT_VALUE",
            "PHANTOM_READ_CONFLICT_VALUE","UNKNOWN_TX_TYPE_VALUE","TARGET_CHAIN_NOT_FOUND_VALUE","MARSHAL_TX_ERROR_VALUE",
            "NIL_TXACTION_VALUE","EXPIRED_CHAINCODE_VALUE","CHAINCODE_VERSION_CONFLICT_VALUE","BAD_HEADER_EXTENSION_VALUE",
            "BAD_CHANNEL_HEADER_VALUE","BAD_RESPONSE_PAYLOAD_VALUE","BAD_RWSET_VALUE","ILLEGAL_WRITESET_VALUE",
            "INVALID_WRITESET_VALUE","NOT_VALIDATED_VALUE","INVALID_OTHER_REASON_VALUE"
    };

}
